/*
 * @Description: Be careful what you wish for
 * @Autor: zzzzzed
 * @Date: 2022-12-03 00:02:06
 * @LastEditors: zzzzzed
 * @LastEditTime: 2022-12-03 00:02:06
 */
/**   
 * MVVM 
 * 
 *  @author IT侠来了
 */
import { deepCopy, lerpNumber } from "../../util/utils";
import { bind } from "./binding"
import Entity from "../ecs/entity"
import HoldSpritesComponent from "../../components/HoldSpritesComponent";
import { NodeAttrExt, NodeExt } from "../../module/pools";
import AnimatorSpine from "../../components/animator/AnimatorSpine";
export type TFunction = { (...args: any[]): void };
export type MvvmBindData = { [key: string]: any }
export type MvvmMethod = { [key: string]: TFunction }
export type MvvmViewData = { [key: string]: MvvmViewDataValue }
export type MvvmViewDataValue = string | ViewBindTag | MvvmViewData | Array<string>
export interface ViewBindTag {

    /**
     * 普通文本
     */
    __text?: string;

    /**
     * 富文本组件
     */
    __richText?: string;

    /** layout 组件 */
    __layout?: cc.Layout;

    /**
     * 对于cc.EditBox 组件 的string 双向绑定
     */
    __editBoxText?: string;

    /**
     *  数字滚动 
     */
    __lerpNum?: string;

    /**
     * 配合 __lerpNum 使用。 滚动间隔
     */
    __duration?: number;

    /**
     *  用于组件上的组件上的spriteFrame属性 适用于 sprite
     */
    __sprite?: string;

    /**
     * node节点的active属性
     */
    __active?: string;

    /**
     * 获取node节点的引用
     */
    __getter?: string;

    /**
     * 适用于给node节点的绑定click事件
     */
    __onClick?: string;

    /**node touchstart 事件 */
    __touchstart?: string;

    /**node touchmove 事件 */
    __touchmove?: string;

    /**node touchend 事件 */
    __touchend?: string;

    /**node touchcancel 事件 */
    __touchcancel?: string;
    /**
     * 表示绑定的是一个动态函数（有运行时的逻辑动态传入，不在methods预定义）
     */
    __dyna?: string;

    /**
     * methods里的key，用来拿到methods预定义的方法
     */
    __func?: string;

    /**
     * 绑定到那个mvvm绑定到Data到属性上 ,该标记可以和__func一起使用让val和oldval 传入到指定的预定义的函数里
     */
    __bind?: string;

    /**
     * 指定mvvm绑定的Data对象上的属性，配合__bind一起使用，如果指定的属性发生改变将劫持
     */
    __field?: string;

    /**
     * 是否需要ecs的实体对象参与，如果没有使用ECS 不必理会改标志
     */
    __ecsEnt?: string;

    /**
     * 保存node的opacity属性
     */
    __opacity?: string;

    /**
    * 保存node的color属性
    */
    __color?: string;

    /**
     * 适用于layout 处理一个或者多个子节点的列表 比如 排行榜
     */
    __list?: ViewBindTag;

    /**
     * 可以在Mvvm 绑定的对象上提前定义一个节点数组，mvvm会将这些节点与节点树进行绑定（需配合__list一起使用）
     */
    __isStatic?: boolean;

    /**
     * 一般和__list使用 表示 list的子节点将动态添加 （需配合__list一起使用）
     */
    __dynamic?: boolean;

    /**
     * 保存子节点的 树结构（需配合__list一起使用）
     */
    __itemView?: MvvmViewData;

    /**
     * 子节点使用的模版节点（该节点必须创建在场景的列表中，mvvm在运行后会将该节点自动移除出当前父节点 .需配合__list一起使用）
     */
    __prefab?: string;

    /**
     * 列表数据绑定前执行的函数名（需定义在methods中 该tag需配合 __list 使用）
     */
    __initFunc?: string;

    /**
     * 列表数据绑定后执行的函数名（需定义在methods中 该tag需配合 __list 使用）
     */
    __afterFunc: string;
    /**
     * 保存node上的HoldSprites组件的引用（HoldSprites组件是自定义的不是Cocos提供的）
     */
    __holdSprites?: string;

    /**
     * 在组件HoldSprites拿指定的名字的图片赋值给cc.Sprite的spriteFrame属性
     */
    __holdSprite?: string;

    /**
     * 保存node上的cc.PageView组件的引用
     */
    __pageView?: string;

    /**
     * 保存node上的cc.ScrollView组件的引用
     */
    __scrollView?: string;

    /**
     * 保存node上的cc.Widget组件的引用
     */
    __widget?: string;

    /**
     * 设置node 的active 同时,如果node的active是true 那么播放一次默认的Animation的clip 在将node的active设置为false
     */
    __activeAnim?: string;

    /**
     * 播放指定动画clip
     */
    __anim?: string;

    /**
     * 返回动画animation
     */
    __animation?: string;

    /**
         * 返回动画animatorSpine状态机
     */
    __animatorSpine?: string;

    /**
     *  暂时只实现了单选 
     */
    __toggle?: string;

    /**
     * 配合 __toggle使用 ，绑定在对象属性上，来操作toggle的isChecked属性
     */
    __isChecked?: string;

    /**
     * 处理 精灵类型是 FILLED 的 fillRange属性
     */
    __fillRange?: string;

    /**
     * 下载一个远程的图片 比如wx的头像
     */
    __spriteUrl?: string;

    /**
     *  更新 cc.Slider 组件的 progress 属性
     *
     */
    __progressBar?: string;

    /**
     *  更新 cc.Slider 组件的 progress 属性
     *
     */
    __sliderProgress?: string;

    /** 绑定 slider 回调 */
    __slider: string;

    /** 龙骨组件 */
    __spine?: sp.Skeleton;
    /** 虚拟列表 节点服用 */
    __scrollList: string;

    /** 虚拟列表 保存所有node*/
    __nodeItems: string;

    /**
     * 作为监听虚拟列表滚动事件回调
     */
    __updateData: string,

    __boxSize: { width: number, height: number }
    // 保存label 组件的引用
    __labelComp: string

}
/**
 * 内部使用 给 cc.Animation组件加[on] 事件
 * @param key 视图绑定标记
 * @param value 绑定回调函数列表里的方法名
 * @param methods mvvm绑定回调函数列表
 * @param node 目标Node
 * @param event 和key是一样
 * @param cocosEvent cocos的事件名
 * @returns 如果绑定成功返回true 反正返回false
 */
const registerAnimCallback = function (key: string, value: string, methods: MvvmMethod, node: cc.Node, event: string, cocosEvent: string): boolean {

    if (key === event) {
        let animation = node.getComponent(cc.Animation);
        let valueType = typeof (value);
        if (valueType === 'string') {
            let method = methods[value];
            animation.on(cocosEvent, method);
        }
        return true;
    }
    return false;

}

/**
 * 内部使用 , 给node节点绑定【on】事件
 * @param node  目标节点
 * @param cocosEvent cocos的事件名
 * @param func  回调函数
 * @param data  为mvvm提供数据的对象
 * @returns 返回 绑定在节点事件上的回调函数，方便关闭
 */
const makeEventFuncOfNode = function (node: cc.Node, cocosEvent: string, func: TFunction, data: MvvmBindData): TFunction {
    let cb = function (...args: any[]) {
        func.call(node, node, ...args, data)
    }

    node.on(cocosEvent, cb);
    return cb;
}

/**
 * 内部使用 绑定组件的属性，绑定的值不直接赋值组件的属性而是通过 传给指定的函数,处理后返回在赋值
 * @param key  视图绑定标记
 * @param targetKey 需要的标记
 * @param component Cocos的组件  比例 cc.Label
 * @param prop 需要绑定的组件的属性 比例 cc.Label的string
 * @param value 视图绑定标记类型的对象
 * @param data mvvm 绑定的对象
 * @param methods mvvm 绑定函数列表
 * @param node 需要绑定的节点
 * @returns  绑定成功返回true 否则返回false
 */
const registerBindFunc = function <T extends cc.Label, K extends keyof T>(key: string, targetKey: string, component: { prototype: T }, prop: K, value: ViewBindTag, data: MvvmBindData, methods: MvvmMethod, node: cc.Node) {

    if (key === targetKey) {
        let comp = node.getComponent<T>(component);
        if (methods[value.__func] && data[value.__bind]) {
            comp[<string>prop] = methods[value.__func](data[value.__bind]);
        }
        bind(data, value.__bind, function (val, old) {
            comp[<string>prop] = methods[value.__func](val, old);
        });
        return true;
    }
    return false;
}

/**
 * 向node绑定 click,touchstart,touchmove,touchend,touchcancel等事件
 * @param key  视图绑定标记
 * @param value 用户自定义字符串 用于和mvvm里面的methods的key一一对应 或者是一个视图绑定标记类型的对象
 * @param methods mvvm 绑定函数列表
 * @param node  需要绑定的节点
 * @param event 需要匹配的视图绑定标记 只有和key 相同才会执行node的绑定事件逻辑
 * @param cocosEvent 需要绑定的cocos事件
 * @param data mvvm 绑定的对象
 * @returns 如果绑定成功返回true 否则返回false
 */
const registerCallback = function (key: string, value: string | ViewBindTag, methods: MvvmMethod, node: cc.Node, event: string, cocosEvent: string, data: MvvmBindData): boolean {

    if (key === event) {

        // 如果value 是字符串那么就是单向绑定静态函数
        if (typeof value === 'string') {
            let method = methods[value];
            node.on(cocosEvent, function (...args: any[]) {
                method.call(node, node, ...args, data);
            });

        } else {
            // 动态绑定函数，在运行时传入一个回调函数，和目标节点绑定

            let func: TFunction;
            //如果没有设置为动态函数,那就是使用预定义的methods里面的函数
            if (!data[value.__dyna]) {
                func = methods[value.__func];
                data[value.__dyna] = func;
            } else {
                // dyna 必须映射一个函数
                if (data[value.__dyna] && typeof data[value.__dyna] === "function") {
                    func = data[value.__dyna];
                } else {
                    throw "__dyna 映射的值必须是一个函数"
                }
            }
            // 保留上一次的函数 方便off，同时吧函数绑定到node的on事件上
            let cb = makeEventFuncOfNode(node, cocosEvent, func, data)
            data[`_${value.__dyna}CB`] = cb;
            bind(data, value.__dyna, function (val, old) {
                node.off(cocosEvent, data[`_${value.__dyna}CB`]);
                if (val !== null) {
                    let cb = makeEventFuncOfNode(node, cocosEvent, val, data);
                    data[`_${value.__dyna}CB`] = cb;
                }
            });

        }

        return true;
    }
    return false;
}



export interface ArrayProtoExt {
    /**
     *  节点和节点树（data上的属性）进行绑定
     */
    set?: { <T>(key: number, val: T, idx?: number, node?: cc.Node): void },

    /**
     * 删除指定位置上的节点数据
     */
    remove?: { (key: number): void },
    /**
     * 删除所有子节点的数据
     */
    removeAll?: { (): void }

    /**
     * 把所有子节点的active都设置成fasle
     */
    hideAll?: { (): void }

    /** 虚拟列表 */
    scrollList?: { (): void }
}

export type MvvmArray<T> = Array<T> & ArrayProtoExt;

// const ScrollList = function (array: Array<any>, value: ViewBindTag, prefab: cc.Node, node: cc.Node, methods: MvvmMethod, data: MvvmBindData): void {
//     /** 让 bind的数组扩展，让子节点都绑定子试图 */
//     ArrayDuress(array, value, prefab, node, methods, data);

// }


/**
 * 创建一个数组类型的对象，适用于layout组件。和layout的每一个子节点进行绑定
 * @param array 定义的数组容器，用于存放和子节点绑定的数据
 * @param value mvvm的视图标记
 * @param prefab 子节点使用的节点 
 * @param node 当前的父节点
 * @param methods mvvm 绑定函数列表 
 * @param data mvvm 绑定的对象
 * @returns 
 */
const ArrayDuress = function (array: Array<any>, value: ViewBindTag, prefab: cc.Node, node: cc.Node, methods: MvvmMethod, data: MvvmBindData): void {

    let arrayMethods = Object.create(Array.prototype);
    let newArrayProto: MvvmArray<any> = [];

    newArrayProto.set = function <T>(key: number, val: T, idx?: number, ___node?: cc.Node) {
        if (value.__nodeItems) {
            deepCopy(val, data[value.__bind][key]);
            return;
        }
        if (data[value.__bind] && data[value.__bind][key] && data[value.__bind][key].node && !___node) {

            let oldNode = data[value.__bind][key].node;
            if (undefined !== idx) {
                oldNode.setSiblingIndex(idx);
            } else {
                oldNode.setSiblingIndex(key);
            }
            deepCopy(val, data[value.__bind][key]);
            oldNode.active = true;
        } else {

            let subNode: cc.Node & NodeExt;
            if (!___node && prefab) {
                subNode = cc.instantiate(prefab);
                node.addChild(subNode);

            } else {
                if (value.__dynamic) {
                    node.addChild(___node);
                }
                subNode = ___node;
            }
            subNode.active = true;
            if (undefined !== idx) {
                subNode.setSiblingIndex(idx);
            } else {
                subNode.setSiblingIndex(key);
            }
            //Todo:看看之后能不用使用getSiblingIndex
            subNode.idx = key;
            data[value.__bind][key] = val;
            data[value.__bind][key].node = subNode;
            if (value.__itemView) {
                mvvm(value.__itemView, data[value.__bind][key], methods, null, subNode);
            }
        }
    }

    newArrayProto.remove = function (key: number) {

        if (data[value.__bind] && data[value.__bind][key]) {
            data[value.__bind][key].node.parent = null;
            data[value.__bind][key].node.destroy();
            data[value.__bind][key] = null;
        }
    }

    newArrayProto.removeAll = function () {
        if (data[value.__bind] && data[value.__bind].length > 0) {
            for (let i = 0; i < data[value.__bind].length; i++) {
                if (data[value.__bind][i] && data[value.__bind][i].node) {
                    if ((data[value.__bind][i].node as cc.Node).parent) {
                        data[value.__bind][i].node.parent = null;
                        data[value.__bind][i].node.destroy();
                    }
                    data[value.__bind][i] = null;
                }

            }
        }
    }

    newArrayProto.hideAll = function () {

        if (data[value.__bind] && data[value.__bind].length > 0) {
            for (let i = 0; i < data[value.__bind].length; i++) {
                if (data[value.__bind][i] && data[value.__bind][i].node) {
                    data[value.__bind][i].node.active = false;
                }
            }
        }
    }

    /**挂在父节点上 */
    newArrayProto.scrollList = function () {

        let originList = deepCopy(data[value.__bind])
        let bindData = data[value.__bind];
        let allItem: NodeExt[] = [];
        if (!Array.isArray(bindData)) {

            console.warn("虚拟列表数据必须是一个数组");
            return;
        }

        if (!bindData.length) {

            console.warn("虚拟列表必须要有初始数据");
            return;
        }

        let scrollView = node.getComponent(cc.ScrollView);

        let nodeParent = scrollView.content;
        nodeParent.removeAllChildren();
        let layout = nodeParent.getComponent(cc.Layout);

        if (!layout) {

            console.warn("内容节点上必须要有layout组件")
            return;
        }

        if (!scrollView) {

            console.warn("绑定scrollview的 节点必须要有ScrollView组件")
            return;
        }
        scrollView.stopAutoScroll();
        scrollView.scrollToTop(0);
        /** 算出可以展示所有Item的最小数量*/
        let viewH = node.height;
        let viewW = node.width;
        let minShowNum = 1;

        switch (layout.type) {
            case cc.Layout.Type.GRID://网格布局
                break;
            case cc.Layout.Type.HORIZONTAL://横向布局
                break;
            case cc.Layout.Type.NONE://没有布局模式
                break;
            case cc.Layout.Type.VERTICAL://竖直布局
                let height = viewH - layout.paddingTop - layout.paddingBottom;
                minShowNum += Math.ceil(height / (prefab.height + layout.spacingY));
                break;
            default:
                console.error("不支持的布局模式!", this.contentLayout.type);
                break;
        }

        /** 谁少用谁的 */
        let needNum = bindData.length < minShowNum ? bindData.length : minShowNum;
        //生成或者销毁多余的Item
        let diff = needNum - allItem.length;
        let diffNum = Math.abs(diff);

        if (diff > 0) {
            for (let i = 0; i < diffNum; i++) {
                allItem.push(cc.instantiate(prefab));
            }

        } else if (diff < 0) {
            for (let i = 0; i < diffNum; i++) {
                let node = allItem.pop();
                if (node.cache) {
                    node.cache.put(<cc.Node>node)
                } else {
                    node.destroy();
                }
            }
        }

        /** 处理数据和子试图绑定 */
        allItem.forEach((subNode, idx: number) => {
            subNode.active = true;
            subNode.parent = nodeParent;
            subNode["index"] = idx;
            mvvm(value.__itemView, bindData[idx], methods, null, subNode);
        });

        //布局完后关闭layout
        layout.enabled = true;
        layout.updateLayout();
        layout.enabled = false;
        //改变content
        switch (layout.type) {
            case cc.Layout.Type.GRID://网格布局
                break;
            case cc.Layout.Type.HORIZONTAL://横向布局
                break;
            case cc.Layout.Type.NONE://没有布局模式
                break;
            case cc.Layout.Type.VERTICAL://竖直布局
                nodeParent.height = bindData.length * prefab.height + (bindData.length - 1) * layout.spacingY
                    + layout.paddingTop + layout.paddingBottom;
                break;
            default:
                console.error("不支持的布局模式!", layout.type);
                break;
        }
        bindData.splice(needNum, bindData.length);
        let preContentPos = nodeParent.position;
        let updateItem = function () {

            if (!preContentPos || allItem.length == 0) return;
            switch (layout.type) {
                case cc.Layout.Type.GRID://网格布局
                    break;
                case cc.Layout.Type.HORIZONTAL://横向布局
                    break;
                case cc.Layout.Type.NONE://没有布局模式
                    break;
                case cc.Layout.Type.VERTICAL://竖直布局
                    /**判断移动方向**/
                    let currentPos = nodeParent.position;
                    /**向下移动**/

                    if (preContentPos.y > currentPos.y) {
                        /** 判断 last item 是否超出了bottom视图 */
                        let item = allItem[allItem.length - 1];
                        let y = node.convertToNodeSpaceAR(nodeParent.convertToWorldSpaceAR(item.position)).y;
                        let bottom = 0;
                        if (node.anchorY == 0.5) {
                            bottom = -node.height / 2 - item.height / 2 - layout.spacingY;
                        } else if (node.anchorY == 1) {
                            bottom = -item.height / 2 - layout.spacingY;
                        }

                        /** 最后一个item 超出了底部的视图 */
                        if (y < bottom) {
                            let oldIndex = 0;
                            if (item["index"] - needNum >= 0) {
                                oldIndex = item["index"];
                                item["index"] -= needNum;
                                item.y += needNum * (prefab.height + layout.spacingY);
                                allItem.unshift(allItem.pop());
                                oldIndex = oldIndex % needNum;
                                data.rankData.set(oldIndex, originList[item["index"]]);
                                preContentPos = currentPos;
                            }

                        }

                    } else {

                        /** 判断第一个item 是否超出了top视图 */
                        let item = allItem[0];
                        let y = node.convertToNodeSpaceAR(nodeParent.convertToWorldSpaceAR(item.position)).y;
                        let top = 0;
                        if (node.anchorY == 0.5) {
                            top = node.height / 2 + item.height / 2 + layout.spacingY;
                        } else if (node.anchorY == 1) {
                            top = item.height / 2 + layout.spacingY;
                        }
                        /** 第一个item 已经上移出view范围 */
                        if (y >= top) {
                            /* 判断是否还有数据没有显示 */

                            if (item["index"] + needNum < originList.length) {
                                let oldIndx = item["index"];

                                item["index"] += needNum;

                                item.y -= needNum * (prefab.height + layout.spacingY);

                                allItem.push(allItem.shift());
                                oldIndx = oldIndx % needNum;

                                data.rankData.set(oldIndx, originList[item["index"]]);
                                preContentPos = currentPos;
                            }
                        }

                    }
                    break;
            }
        }

        node.on("scrolling", updateItem);
        node.on("scroll-ended", updateItem);
    }


    array = Object.setPrototypeOf(array, newArrayProto)

    if (value.__dynamic) {
        return;
    }
    if (value.__nodeItems) {

        return;
    }

    if (!prefab) {

        if (!node) {
            if (data.node) {

                node = data.node;
            } else {

                return;
            }
        }

        for (let i = 0; i < node.children.length; i++) {

            let __sNode = node.children[i];
            array["set"](i, array[i] || {}, i, __sNode);
        }
    } else {

        if (array.length) {
            for (let i = 0; i < array.length; i++) {
                array["set"](i, array[i]);
            }
        }
    }

}


/**
 * mvvm初始化方法：
 * 遍历节点数，按定义的规则绑定到对象的属性上，能实现对象到节点的单向或者双向绑定
 * 
 * @param view mvvm 的视图（节点树）需要和场景内节点树保持一致
 * @param value 可以是字符串 或者 视图标记 或者 作为子树的根节点 保存树下的子节点信息
 * @param data mvvm 绑定的对象
 * @param methods mvvm 绑定函数列表
 * @param path 可选 节点树路径用作为 cc.find的第一个参数
 * @param parent 可选 根节点用作为cc.find的第二个参数
 * @param ent 可选 ECS实体对象
 */
export const mvvm = function (view: MvvmViewData, data: MvvmBindData, methods: MvvmMethod, path?: string, parent?: cc.Node, ent?: Entity) {

    for (let key in view) {
        if (key.endsWith('/')) {
            throw new Error('路径不能由/结尾');
        }
        action(key, view[key], data, methods, path, parent, ent);
    }
    data["isBinding"] = true;
}

/**
 * 内部使用 mvvm的核心函数：
 * 遍历节点数，按定义的规则绑定到对象的属性上，能实现对象到节点的单向或者双向绑定
 * 
 * @param key 节点树上的节点名。需要和场景内节点树保持一致
 * @param value 可以是字符串 或者 视图标记 或者 作为子树的根节点 保存树下的子节点信息
 * @param data mvvm 绑定的对象
 * @param methods mvvm 绑定函数列表
 * @param path 可选 节点树路径用作为 cc.find的第一个参数
 * @param parent 可选 根节点用作为cc.find的第二个参数
 * @param ent 可选 ECS实体对象
 */
let action = function (key: string, value: MvvmViewDataValue, data: MvvmBindData, methods: MvvmMethod, path?: string, parent?: cc.Node, ent?: Entity) {
    let node: cc.Node;
    //如果有path(路经)那么直接使用path来查找node
    if (path) {
        node = cc.find(path);
    } else if (path && parent) {
        // 如果有path 和parent 那么 就在parent下找node
        node = cc.find(path, parent);
    } else if (!path && parent) {
        // 如果没有path 有parent 那么 node 就是parent
        node = parent;
    }
    try {

        if (key === "__getter" && typeof value === "string") {

            bind(data, value, function (val, old) {
                if (val === null) {
                    node.destroy();
                }
            });
            data[<string>value] = node;

        } else if (key === '__opacity' && typeof value === "string") {

            let old = data[value];
            bind(data, value, function (val, old) {
                node.opacity = val;
            });
            data[value] = old;

        } else if (key === '__color' && typeof value === "string") {

            node.color = data[value] || node.color;
            bind(data, value, function (val, old) {
                node.color = val;
            });

        } else if (key === '__lerpText') {
            let label = node.getComponent(cc.Label);
            let duration = (<ViewBindTag>value).__duration || 1;
            label.string = data[(<ViewBindTag>value).__bind] || label.string;

            bind(data, (<ViewBindTag>value).__bind, function (val, old) {

                lerpNumber(node, duration, 0, val, function (newVal: number) {
                    newVal = newVal | 0;
                    label.string = newVal + "";
                });
            })

        } else if (key === '__richText' && typeof value === "string") {
            let _richText = node.getComponent(cc.RichText);
            _richText.string = data[value] || _richText.string;
            bind(data, value, function (val, old) {
                _richText.string = val;
            });
        } else if (key === "__boxSize" && typeof value === "string") {
            let { width = 0, height = 0 } = data[value];
            bind(data, value, function (val, old) {
                let { width = 0, height = 0 } = val;

                node.width = width;
                node.height = height;
            });
            data[value] = { width, height };

        } else if (key === '__btnInteractable' && typeof value === "string") {
            let button = node.getComponent(cc.Button);
            bind(data, value, function (val, old) {
                button.interactable = val;
            });
        } else if (key === "__text" && typeof value === "string") {

            let label = node.getComponent(cc.Label);
            label.string = data[value] !== undefined ? data[value] : label.string;
            bind(data, value, function (val, old) {

                label.string = val;
            });

        } else if (key === "__richText" && typeof value === "string") {

            let comp = node.getComponent(cc.RichText);
            comp.string = data[value] || comp.string;
            bind(data, value, function (val, old) {
                comp.string = val;
            });

        } else if (key === "__layout" && typeof value === "string") {
            let comp = node.getComponent(cc.Layout);
            data[value] = comp;
        } else if (key === "__pageView" && typeof value === "string") {

            let pageView = node.getComponent(cc.PageView);
            data[value] = pageView;

        } else if (key === "__widget" && typeof value === "string") {

            data[value] = node.getComponent(cc.Widget);

        }
        else if (key === '__scrollView' && typeof value === "string") {

            const scrollView = node.getComponent(cc.ScrollView);
            data[value] = scrollView;

        } else if (key === '__holdSprites' && typeof value === "string") {

            let holdSprites = node.getComponent(HoldSpritesComponent);
            data[value] = holdSprites;

        } else if (key === '__bind' && (<ViewBindTag>value).__func && (<ViewBindTag>value).__field) {


            if ((<ViewBindTag>value).__ecsEnt) {
                methods[(<ViewBindTag>value).__func](node, data[(<ViewBindTag>value).__field], data[(<ViewBindTag>value).__field], data, ent);
            } else {
                methods[(<ViewBindTag>value).__func](node, data[(<ViewBindTag>value).__field], data[(<ViewBindTag>value).__field], data);
            }


            bind(data, (<ViewBindTag>value).__field, function (val, old) {

                if ((<ViewBindTag>value).__ecsEnt) {
                    methods[(<ViewBindTag>value).__func](node, val, old, data, ent);
                } else {
                    methods[(<ViewBindTag>value).__func](node, val, old, data);
                }
            });

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__onClick', 'click', data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__touchstart', cc.Node.EventType.TOUCH_START, data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__touchmove', cc.Node.EventType.TOUCH_MOVE, data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__touchend', cc.Node.EventType.TOUCH_END, data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__touchcancel', cc.Node.EventType.TOUCH_CANCEL, data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__editing-return', 'editing-return', data)) {

        } else if (registerCallback(key, <ViewBindTag | string>value, methods, node, '__slider', 'slide', data)) {

        } else if (registerBindFunc(key, '__text', cc.Label, 'string', <ViewBindTag>value, data, methods, node)) {

        } else if (registerAnimCallback(key, <string>value, methods, node, '__animFinished', 'finished')) {

        } else if (key === '__activeAnim' && typeof value === "string") {

            let old = !!data[value];
            bind(data, value, function (val, old) {
                node.active = val;
                if (node.active) {
                    let anim = node.getComponent(cc.Animation);
                    anim.play();
                    anim.unscheduleAllCallbacks();
                    anim.scheduleOnce(() => {
                        node.active = false;
                    }, anim.defaultClip.duration);
                }
            });
            data[value] = old;
        } else if (key === '__anim' && typeof value === "string") {

            let old = !!data[value];
            bind(data, value, function (val, old) {
                if (val) {
                    let anim = node.getComponent(cc.Animation);
                    anim.play(val);

                } else {
                    let anim = node.getComponent(cc.Animation);
                    anim.stop();
                }
            });
            data[value] = old;
        } else if (key === '__animation' && typeof value === "string") {
            data[value] = node.getComponent(cc.Animation);
        } else if (key === '__animatorSpine' && typeof value === 'string') {
            data[value] = node.getComponent(AnimatorSpine);
        }else if (key === "__spine" && typeof value === "string") {
            data[value] = node.getComponent(sp.Skeleton);
        } else if (key === '__active') {

            if (value instanceof Array) {
                let target = value[1];
                bind(data, value[0], function (val, old) {
                    if (typeof target === "boolean") {
                        node.active = !!(val) === target;
                    } else {
                        node.active = val === target;
                    }
                });
                data[value[0]] = data[value[0]];
            } else {
                bind(data, <string>value, function (val, old) {
                    node.active = val;
                });
                data[<string>value] = data[<string>value];
            }

        } else if (key === '__holdSprite' && typeof value === "string") {

            let holdSprites = node.getComponent(HoldSpritesComponent);
            let sprite = node.getComponent(cc.Sprite);
            data[value] && (sprite.spriteFrame = holdSprites.get(data[value]));
            bind(data, value, function (val, old) {
                sprite.spriteFrame = holdSprites.get(val);
            });

        } else if (key === '__fillRange' && typeof value === "string") {
            let sprite = node.getComponent(cc.Sprite);
            if (sprite.type !== cc.Sprite.Type.FILLED) {
                throw new Error('__fillRange需要sprite type 为 FILLED');
            }

            sprite.fillRange = data[value] || 0;
            bind(data, value, function (val, old) {
                sprite.fillRange = val;
            });
        } else if (key === '__filledSpriteNode' && typeof value === "string") {
            let sprite = node.getComponent(cc.Sprite);
            if (sprite.type !== cc.Sprite.Type.FILLED) {
                throw new Error('__fillRange需要sprite type 为 FILLED');
            }
            data[value] = sprite;

        } else if (key === '__spriteNode' && typeof value === "string") {
            let sprite = node.getComponent(cc.Sprite);
            if (sprite.type !== cc.Sprite.Type.FILLED) {
                throw new Error('__fillRange需要sprite type 为 FILLED');
            }

            data[value] = sprite;

        } else if (key === '__labelComp' && typeof value === "string") {
            data[value] = node.getComponent(cc.Label);
        } else if (key === '__sprite' && typeof value === "string") {
            let sprite = node.getComponent(cc.Sprite);
            sprite.spriteFrame = data[value] || sprite.spriteFrame;
            if (!data[value]) {
                data[value] = sprite.spriteFrame;
            }
            bind(data, value, function (val, old) {
                sprite.spriteFrame = val;
            });
        } else if (key === '__spriteUrl' && typeof value === "string") {
            let sprite = node.getComponent(cc.Sprite);

            if (data[value] && data[value] !== '') {

                cc.assetManager.loadRemote<cc.Texture2D>(
                    data[value]
                    , function (err: Error, texture: cc.Texture2D) {
                        if (!err) {
                            try {
                                if (true === sprite.isValid) {
                                    sprite.spriteFrame = new cc.SpriteFrame(texture);
                                }
                            } catch (e) {
                                console.log('e', e);

                            }
                        } else {
                            console.error("下载图片失败", err)
                        }
                    });
            }

            bind(data, value, function (val, old) {
                if (val !== '' && val !== old) {

                    cc.assetManager.loadRemote<cc.Texture2D>(
                        val
                        , function (err: Error, texture) {
                            if (!err) {
                                try {
                                    if (true === sprite.isValid) {
                                        sprite.spriteFrame = new cc.SpriteFrame(texture);
                                    }
                                } catch (e) {
                                    console.log('e', e);
                                }
                            } else {
                                console.error("下载图片失败", err)
                            }
                        });
                }
            });
        } else if (key === "__sliderProgress" && typeof value === "string") {
            let slider = node.getComponent(cc.Slider);
            slider.progress = data[value] || 0;
            bind(data, value, function (val, old) {
                slider.progress = val;
            });
        } else if (key === "__progressBar" && typeof value === "string") {
            let progressBar = node.getComponent(cc.ProgressBar);
            progressBar.progress = data[value] || 0;
            bind(data, value, function (val, old) {
                progressBar.progress = val;
            });
        } else if (key === '__editBoxText' && typeof value === "string") {
            let editBox = node.getComponent(cc.EditBox);
            editBox.string = data[value] || editBox.string;
            node.on('text-changed', () => {
                data[value] = editBox.string;
            });
            bind(data, value, function (val, old) {
                editBox.string = val;
            });
        } else if (key === '__list') {

            if ((<ViewBindTag>value).__initFunc) {
                methods[(<ViewBindTag>value).__initFunc](node || data.node);
            }

            let prefab: cc.Node | null
            if ((<ViewBindTag>value).__prefab) {

                prefab = cc.find((<ViewBindTag>value).__prefab, node);
                prefab.active = false;
                prefab.parent = null;

            } else {
                prefab = null
            }

            !data[(<ViewBindTag>value).__bind] && (data[(<ViewBindTag>value).__bind] = []);
            ArrayDuress(data[(<ViewBindTag>value).__bind], <ViewBindTag>value, prefab, node, methods, data);
        } else if (key === "__scrollList") {
            /**  虚拟列表*/
            if ((<ViewBindTag>value).__initFunc) {
                methods[(<ViewBindTag>value).__initFunc](node || data.node);
            }

            let prefab: cc.Node | null
            if ((<ViewBindTag>value).__prefab) {

                prefab = cc.find((<ViewBindTag>value).__prefab, node);
                prefab.active = false;
                prefab.parent = null;

            } else {
                prefab = null
            }

            !data[(<ViewBindTag>value).__bind] && (data[(<ViewBindTag>value).__bind] = []);
            ArrayDuress(data[(<ViewBindTag>value).__bind], <ViewBindTag>value, prefab, node, methods, data);
            if ((<ViewBindTag>value).__afterFunc) {
                methods[(<ViewBindTag>value).__afterFunc](node || data.node);
            }
        } else if (key === '__toggle') {
            let toggle = node.getComponent(cc.Toggle);
            if ((<ViewBindTag>value).__isChecked) {
                toggle.isChecked = !!data[(<ViewBindTag>value).__isChecked];
                bind(data, (<ViewBindTag>value).__isChecked, function (val, old) {
                    if (val !== toggle.isChecked) {
                        toggle.isChecked = val;
                    }
                });
            }

            toggle.node.on('toggle', function (toogle: cc.Toggle) {
                if ((<ViewBindTag>value).__isChecked) {
                    data[(<ViewBindTag>value).__isChecked] = toogle.isChecked;
                }
                if ((<ViewBindTag>value).__func) {
                    methods[(<ViewBindTag>value).__func].call(this, toogle, data);
                }
            }, this);
        } else {
            let subNode: cc.Node;
            //如果没有path和parent 那么直接使用当前路径拿到node
            if (!path && !parent) {
                subNode = cc.find(key);
            } else if (parent && !path) {
                // 有parent 就在parent下面找
                subNode = cc.find(key, parent);
            } else if (path && !parent) {
                //有path，没有parent 就用path+“/”+key拿node
                subNode = cc.find(path + '/' + key);
            }

            // 看看value是否是一个字符串，如果是字符串那么默认是绑定lable
            if (typeof value === "string") {
                let lable = node.getComponent(cc.Label);
                lable.string = data[value] || "";
                bind(data, value, function (val, old) {
                    lable.string = val;
                });
            } else if (typeof value === "object") {
                mvvm(<MvvmViewData>value, data, methods, null, subNode, ent);
            }
        }

    } catch (err) {
        console.error('err', err.stack, 'key:', key, 'value:', value, 'path:', path, 'parent', parent, 'node', node, 'data', data);
    }
}